JS中的函数

JavaScript中的函数

定义:函数是一段可以反复调用的代码块。函数还能接受输入的参数,不同的参数会返回不同的值。

函数的三种定义方式

1、使用function关键字定义

function add(num1, num2) {
    return num1 + num2;
};

2、函数表达式(var 赋值语句)

 var add = function(num1, num2) {
    return num1 + num2;
};

3、Function 构造函数

 var add = new Function('num1','num2','return num1 + num2;');   

三种定义函数方式的区别:

1、前两种定义函数的方式,对比第三种使用Function构造函数创建函数的方式,具有简洁,直观,方便书写的优势。

2、构造函数声明函数的效率来说相对比较低(使用Function构造函数创建函数,第一步要解析字符串,第二部才是实例化函数)

3、第一种与第二和第三种相比来说,第一种是直接使用function关键字声明,而后两种都是借助了变量,用的赋值语句,后面两种声明的方式都要记得尾部带上分号,而第一种不用

4、JS解析器对于三种定义函数的方式带来的影响 在JS预解析中,在js代码开始执行之前,会将function关键字以及var关键字提前解析

  function add1(num1, num2) { // ==> 会将函数add1()整体提到最前,解析器提前知道add1为函数
    return num1 + num2;
  }

  var add2 = function(num1, num2) { // ==> 会将add2提到最前,并且将add2赋值为undefined
    return num1 + num2; 
  };

所以,在后续调用的过程中,即使在js文件顶部,调用add1函数,也没有任何问题,而由于js预解析将add2赋值为了undefined,所以在声明之前调用,会出现报错。

函数定义的位置

在全局作用域中定义

  fn() //==> 可以调用 
  function fn() {
    .functionBody...
    fn()// ==> 可以调用
  }
  fn() //==> 可以调用
// 在全局作用域中定义使用function关键字定义的函数,可以在声明函数之前调用(js预解析机制),可以在函数声明之后调用,也可以在函数体内部调用

在函数作用域下定义(局部作用域)

 function fn(){
    fn() // ==> 可以访问
    fn1() // ==>可以访问
    fn2() // ==>可以访问
    function fn1() {
       fn2() // ==> 可以访问
    }

    function fn2() {
       fn1() // ==> 可以访问
    }
    fn() // ==>可以访问
    fn1() // ==>可以访问
    fn2() // ==>可以访问
 }

// 在fn函数内部定义的函数,在fn外部(无论前后)是不能访问调用的
//在fn函数内部定义的函数,遵循作用域链机制,既越是内层的函数,越能调用(访问)上一层的函数

作用域链png.png作用域链png.png

不能在条件语句中声明函数

   if (true) {
    function add() {
        alert(1);
    }
   } else {
    function subtract() {
        alert(2);
    }
   }

// 这样声明函数的方式是错误的

js中只有全局作用域和函数内局部作用域,没有代码块(块级作用域),上面的声明方式,相当于是在全部作用域中定义两个相同名字的函数,js解析器会对上面使用function关键字的函数进行预解析,最终两个函数都会被声明

==今天在自己测试中,发现最新的chrome和Firefox都支持上面的声明模式,而在ie中(自己的是ie9),还是上述规律再次查询,得到了如下答案==

这其实是个历史遗留问题……

以前在ES5的时候,规范规定函数只能在顶层作用域和函数作用域之中声明,不能在块级作用域声明。所以,类似这样的语句其实都是非法的:
if (true) {
    function f() {}
}

但是实际上各大浏览器出于兼容性的考虑,都没有遵守这个规范。

到了现在ES6的年代,规范规定了块级作用域的存在,函数就可以在块级作用域中定义了。 
但其实事情并没有这么简单,因为这样的话,函数的定义行为就和以前不兼容了,为了保证和以前的兼容性,ES6在附录B里面规定,浏览器的实现可以不遵守上面的规定,有自己的行为方式。

在ES6的浏览器中,它们的行为实际上是这样的:

允许块级作用域中定义函数

函数声明实际上将会类似于使用var声明的函数表达式,函数名将会提升至当前函数作用域顶

同时函数声明也会保持在块级作用域中的提升行为

https://segmentfault.com/q/1010000009718528

简单来说,就是ES6规定了块级作用域,并且各大浏览器也有自己的标准,所以在标准没有统一之前,还是不建议按照上述方式定义函数

==如果在代码需求必须要在代码块中定义的情况下,推荐可以使用如下方式定义函数==

if (true) {
   var fn1 = function() {....}
} else {
   var fn2 = function() {....}
}

这是由于预解析的时候,使用var定义的变量,只会被预先赋值为undefined,而不是会当做function关键字直接给定义了

对象中的函数(方法)

  var obj = {
     name:'xm',
     say:function(){
    alert(this.name)
     }
  }

函数的调用

普通函数的调用

命名函数的调用

   function add(num1, num2) {
    return num1 + num2;
   }

   // 使用函数名+()来实现调用
   console.log(add(1,2)) // ==> 3

匿名函数的调用

   function(num1,num2) {
    return num1 + num2;
   }
//上述匿名函数,直接写在js文件中运行会报错,更谈不上调用了

可以使用如下两种方式调用 ==第一种调用方式:将匿名函数赋值给变量,通过变量调用==

  var add = function(num1, num2) {
    return num1 + num2;
  }
//通过变量名调用
console.log(add(1,2)) //==> 3

==第二种调用方式,在声明的同时调用==

在使用此类方式调用时,要注意不要让function打头
  (function(){
     console.log(1+1);
  })();

  (function(){
     console.log(1+2);
  }());

  -function(){
     console.log(1+3);
  }();

  +function(){
     console.log(1+4);
  }();

  +-!~function(){
     console.log(1+5)
  }();

  console.log(
    function(){
     return(1+6);
    }()
  )

这是因为,js解析器只要遇到function关键字,就认为此时在进行函数声明,并且进行预解析,所以在声明的同时运行,是会出现报错!这时,只需要让function关键字不打头,在匿名函数尾部加上()进行调用

普通函数的递归调用(自己调用自己)

需求:写一个函数,实现5的阶乘 阶乘实现的过程为 5×4×3×2×1

  function factorial(num) {
     if(num == 1) return 1;
     return num * factorial(num - 1);
  }
  factorial(5) //==>120

构造函数的调用

定义:构造函数 ,是一种特殊的方法。主要用来在创建对象时初始化对象 Js自带的构造函数 Array Object Function Math RegExp...

调用的时候,需要使用new关键字

 var arr = new Array(1,2,3);
 var obj = new Object();
 var fn = new Function();
 var regexp = new RegExp();
 ....

//自定义构造函数
function Person(name,age){
  this.name = name;
  this.age = age;
}

//调用自定义构造函数
var person1 = new Person('xm',18)

函数的间接调用

函数的间接调用,需要借助每个函数都自带的两个方法: 1、call() 2、apply()

apply()方法 function.apply(thisObj[, argArray])

call()方法 function.call(thisObj[, arg1[, arg2[, [,...argN]]]]);

两种方法只有第二个参数不同,如果只传第一个参数,两个方法的使用时完全一样的,第一个参数用来改变函数中this的指向,apply方法第二个参数期待接收的是一个数组,而call方法后面的参数时一个一个传入的。

        var name = 'xm';
        var person = {
            name:'xh',
            getName:function() {
                return this.name;
            }
         }
         console.log(person.getName())  //==> xh
         console.log(person.getName.call(window)) // ==> xm
     console.log(person.getName.apply(window)) // ==> xm

面试题:使用一种方法,取出数组中的最大值,越简单越好

        console.log(Math.max(1,2,33,44,66,99)) //==>99
        var arr = [1,2,33,44,66,99];
        console.log(Math.max.call(window,1,2,33,44,66,99))//==>99
        console.log(Math.max.apply(window,arr))//==>99

面试题:封装一个方法,该方法可以接收任意个参数,并对接收的参数或数组求和

       function add() {
        var result = 0;
        for (var i = 0; i < arguments.length; i++) {
            result += arguments[i];
        }
        return result;
       }
       console.log(add(1,2));
       var arr = [1,2,33,44,66,99];
       console.log(add.apply(window,arr));